package utils

import (
	"bufio"
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"time"
	"net/http"
	"io/ioutil"
	"encoding/json"
	"sync"
)

func GetSelfBinDirPath() string {
	selfBin, _ := exec.LookPath(os.Args[0])
	strBinPath, _ := filepath.Abs(selfBin)
	return filepath.Dir(strBinPath)
}


func StringToMap(ignoreString string, ignoreMap map[string]interface{}){
	var ignoreArr []string
	ignoreArr = strings.Split(ignoreString,",")
	for i :=0; i< len(ignoreArr);i++  {
		ignoreMap[ignoreArr[i]]=1
	}
}

func LoadJsonFromFile(path string, v interface{}) error {
	bytes, err := ioutil.ReadFile(path)
	if nil != err {
		return err
	}
	if err = json.Unmarshal(bytes, v); nil != err {
		return err
	}
	return nil
}

func ExecCmd(intTimeout int, outWriter io.Writer, errWriter io.Writer, allWriter io.Writer, strBin string, args ...string) error {
	cmd := exec.Command(strBin, args...)
	stdout, err := cmd.StdoutPipe()
	if nil != err {
		return err
	}
	defer stdout.Close()
	outReader := bufio.NewReaderSize(stdout, 1024*1024*5)

	stderr, err := cmd.StderrPipe()
	if nil != err {
		return err
	}
	defer stderr.Close()
	errReader := bufio.NewReaderSize(stderr, 1024*1024*5)

	err = cmd.Start()
	if nil != err {
		return err
	}

	mutex := new(sync.Mutex)
	boolDone := make(chan bool, 2)
	go func() {
		for {
			var bytesLine []byte
			bytesLine, _, err = errReader.ReadLine()
			if io.EOF == err {
				break
			}
			if nil != err {
				break
			}

			if nil != allWriter {
				mutex.Lock()
				bytesLine = append(bytesLine, '\n')
				allWriter.Write(bytesLine)
				mutex.Unlock()
			}
			if nil != errWriter {
				bytesLine = append(bytesLine, '\n')
				errWriter.Write(bytesLine)
			}
		}
		boolDone <- true
	}()
	go func() {
		for {
			var bytesLine []byte
			bytesLine, _, err = outReader.ReadLine()
			if io.EOF == err {
				break
			}
			if nil != err {
				break
			}
			if nil != allWriter {
				mutex.Lock()
				bytesLine = append(bytesLine, '\n')
				allWriter.Write(bytesLine)
				mutex.Unlock()
			}
			if nil != outWriter {
				bytesLine = append(bytesLine, '\n')
				outWriter.Write(bytesLine)
			}
		}
		boolDone <- true
	}()

	select {
	case <-boolDone:
		err = cmd.Wait()
		break

	case <-time.After(time.Duration(intTimeout) * time.Second):
		cmd.Process.Kill()
		cmd.Wait()
		err = errors.New("timeout")
		break
	}

	return err
}

func CalcFileMd5(strFilePath string) (string, error) {
	fileHandle, err := os.Open(strFilePath)
	if nil != err {
		return "", err
	}

	md5Instance := md5.New()
	io.Copy(md5Instance, fileHandle)
	return fmt.Sprintf("%x", md5Instance.Sum([]byte(""))), nil
}

func getCallerInfo(intSkip int) string {
	_, strFile, intLine, _ := runtime.Caller(intSkip)
	listItem := strings.Split(strFile, "/")
	strFile = listItem[len(listItem)-1]

	return fmt.Sprintf("file-%s, line-%d", strFile, intLine)
}

func RecordLog(logger *log.Logger, boolError bool, intSkip int, strFormat string, args ...interface{}) {
	strTag := "v"
	if boolError {
		strTag = "e"
	}
	strLog := fmt.Sprintf(fmt.Sprintf("[%s/%s] %s\n", strTag, getCallerInfo(intSkip), strFormat), args...)
	if nil != logger {
		logger.Printf(strLog)
	} else {
		log.Printf(strLog)
	}
	log.Printf(strLog)
}

func IsDirExisted(strDirPath string) bool {
	handlePath, err := os.Stat(strDirPath)
	if nil != err {
		return os.IsExist(err)
	} else {
		return handlePath.IsDir()
	}
}

func getObjTypeName(obj interface{}) string {
	strObjTypeName := reflect.TypeOf(obj).String()
	return strObjTypeName[strings.LastIndex(strObjTypeName, ".")+1:]
}

func KillProcessByPid(intPid int) error {
	process, err := os.FindProcess(intPid)
	if nil != err {
		return err
	}

	return process.Kill()
}

func CopyFile(strSrcFilePath, strDstFilePath string) error {
	fileSrc, err := os.Open(strSrcFilePath)
	if nil != err {
		return err
	}
	defer fileSrc.Close()

	fileDst, err := os.OpenFile(strDstFilePath, os.O_WRONLY|os.O_CREATE, 0644)
	if nil != err {
		return err
	}
	defer fileDst.Close()

	_, err = io.Copy(fileDst, fileSrc)
	return err
}

func IsFileExisted(strFilePath string) bool {
	_, err := os.Stat(strFilePath)
	return err == nil || os.IsExist(err)
}

func HttpGetWithUA(url string, ua string) ([]byte, error) {
	client := &http.Client{}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("User-Agent", ua)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	return ioutil.ReadAll(resp.Body)
}